package com.daxue.configtest.utils;

import com.fasterxml.jackson.databind.JsonNode;
import cz.jirutka.rsql.parser.ast.AndNode;
import cz.jirutka.rsql.parser.ast.ComparisonNode;
import cz.jirutka.rsql.parser.ast.NoArgRSQLVisitorAdapter;
import cz.jirutka.rsql.parser.ast.Node;
import cz.jirutka.rsql.parser.ast.OrNode;
import cz.jirutka.rsql.parser.ast.RSQLOperators;
import org.jooq.Condition;
import org.jooq.Field;
import org.springframework.util.StringUtils;

import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static com.daxue.configtest.others.AlertUtils.alertMessage;
import static com.daxue.configtest.utils.JsonNodeUtils.WILDCARD_JSON_FILED_PATTERN;
import static com.daxue.configtest.utils.JsonNodeUtils.keyStringToKeys;
import static com.daxue.configtest.utils.JsonNodeUtils.wrapByKeys;
import static com.daxue.configtest.utils.JsonNodeUtils.wrapNullByKeys;
import static com.daxue.configtest.utils.ObjectMapperUtils.asStringSerenely;
import static org.jooq.impl.DSL.condition;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.name;

/**
 * @author daxue0929
 * @date 2023/3/25
 */

public class QueryVisitor extends NoArgRSQLVisitorAdapter<Condition> {

    private static final AbstractCompositeConditionEvaluator[] evaluators = {
        new JsonCompositeConditionEvaluator(),
        new CompositeConditionEvaluator()
    };

    private Condition visit(final Node node) {
        if (node instanceof AndNode) {
            return visit((AndNode) node);
        }
        if (node instanceof OrNode) {
            return visit((OrNode) node);
        }
        if (node instanceof ComparisonNode) {
            return visit((ComparisonNode) node);
        }
        throw new IllegalArgumentException(alertMessage("Invalid node type: %s", node.getClass().getName()));
    }



    @Override
    public Condition visit(AndNode node) {
        Stream<Condition> conditionStream = node.getChildren().stream().map(this::visit);

        Optional<Condition> reduce = conditionStream.reduce(Condition::and);
        Condition condition = reduce.get();
        return condition;

//        return node.getChildren().stream().map(this::visit).reduce(Condition::and)
//            .orElseThrow(() -> new IllegalArgumentException("Invalid logic and node: " + node));
    }

    @Override
    public Condition visit(OrNode node) {
        return node.getChildren().stream().map(this::visit).reduce(Condition::or)
            .orElseThrow(() -> new IllegalArgumentException("Invalid logic or node: " + node));
    }


    @Override
    public Condition visit(ComparisonNode node) {

        // try handle possible json field
        for (AbstractCompositeConditionEvaluator evaluator : evaluators) {
            if (evaluator.matchArgument(node)) {
                return evaluator.eval(node);
            }
        }


        throw new IllegalArgumentException(alertMessage("Unsupported node type: %s", node));
    }

    interface ConditionEvaluator<T> {
        Field evalSelector(ComparisonNode node);
        T evalArgument(ComparisonNode node);
        boolean matchArgument(ComparisonNode node);
        Condition eval(ComparisonNode node);
    }


    static abstract class AbstractConditionEvaluator<T> implements ConditionEvaluator<T> {
        @Override
        public Field evalSelector(final ComparisonNode node) {
            return field(name(node.getSelector()));
        }
    }

    static class NullConditionEvaluator extends AbstractConditionEvaluator<Void> {

        final static String NULL_VALUE = "null";

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() == 1 && node.getArguments().get(0).equals(NULL_VALUE);
        }

        @Override
        public Void evalArgument(final ComparisonNode node) {
            return null;
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                return evalSelector(node).isNull();
            }
            if (RSQLOperators.NOT_EQUAL.equals(node.getOperator())) {
                return evalSelector(node).isNotNull();
            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }

    }

    static class NumberConditionEvaluator extends AbstractConditionEvaluator<Number> {

        private static final Pattern INTEGER_VALUE_PATTERN = Pattern.compile("^\\$[+-]?\\d+[Ll]?$");
        private static final Pattern DECIMAL_VALUE_PATTERN = Pattern.compile("^\\$[+-]?(\\d+)?\\.\\d+[dDfF]?$");

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() == 1 &&
                (INTEGER_VALUE_PATTERN.matcher(node.getArguments().get(0)).matches() ||
                    DECIMAL_VALUE_PATTERN.matcher(node.getArguments().get(0)).matches());
        }

        @Override
        public Number evalArgument(final ComparisonNode node) {
            String numberStr = node.getArguments().get(0).replace("$", "").toLowerCase();
            if (!numberStr.contains(".")) {
                if (numberStr.endsWith("l")) {
                    return Long.parseLong(numberStr.replace("l", ""));
                }
                return Integer.parseInt(numberStr);
            }
            if (numberStr.toLowerCase().endsWith("f")) {
                return Float.parseFloat(numberStr.replace("f", ""));
            }
            return Double.parseDouble(numberStr.replace("d", ""));
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            Field field = evalSelector(node);
            Number value = evalArgument(node);
            if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                return field.eq(value);
            }
            if (RSQLOperators.NOT_EQUAL.equals(node.getOperator())) {
                return field.ne(value);
            }
            if (RSQLOperators.GREATER_THAN.equals(node.getOperator())) {
                return field.greaterThan(value);
            }
            if (RSQLOperators.GREATER_THAN_OR_EQUAL.equals(node.getOperator())) {
                return field.greaterOrEqual(value);
            }
            if (RSQLOperators.LESS_THAN.equals(node.getOperator())) {
                return field.lessThan(value);
            }
            if (RSQLOperators.LESS_THAN_OR_EQUAL.equals(node.getOperator())) {
                return field.lessOrEqual(value);
            }
//            if (RSQLOperators.IN.equals(node.getOperator())) {
//                return field.eq(value);
//            }
//            if (RSQLOperators.NOT_IN.equals(node.getOperator())) {
//                return field.ne(value);
//            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }

    }

    static class TimestampConditionEvaluator extends AbstractConditionEvaluator<Timestamp> {

        private static final Pattern TIMESTAMP_VALUE_PATTERN = Pattern.compile("^@\\d+[Ll]?$");

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() == 1 &&
                TIMESTAMP_VALUE_PATTERN.matcher(node.getArguments().get(0)).matches();
        }

        @Override
        public Timestamp evalArgument(final ComparisonNode node) {
            return new Timestamp(Long.parseLong(node.getArguments().get(0).replace("@", "")));
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            Field field = evalSelector(node);
            Timestamp value = evalArgument(node);
            if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                return field.eq(value);
            }
            if (RSQLOperators.GREATER_THAN.equals(node.getOperator())) {
                return field.greaterThan(value);
            }
            if (RSQLOperators.GREATER_THAN_OR_EQUAL.equals(node.getOperator())) {
                return field.greaterOrEqual(value);
            }
            if (RSQLOperators.LESS_THAN.equals(node.getOperator())) {
                return field.lessThan(value);
            }
            if (RSQLOperators.LESS_THAN_OR_EQUAL.equals(node.getOperator())) {
                return field.lessOrEqual(value);
            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }

    }

    static class StringConditionEvaluator extends AbstractConditionEvaluator<String> {

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() == 1;
        }

        @Override
        public String evalArgument(final ComparisonNode node) {
            return node.getArguments().get(0);
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            Field field = evalSelector(node);
            String value = evalArgument(node);
            if (value.contains("*")) {
                if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                    return field.like(value.replace("*", "%"));
                }
                if (RSQLOperators.NOT_EQUAL.equals(node.getOperator())) {
                    return field.notLike(value.replace("*", "%"));
                }
            }
            Field valueFile = field("'" + value.replace("'", "''") + "'");
            if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                return field.eq(valueFile);
            }
            if (RSQLOperators.NOT_EQUAL.equals(node.getOperator())) {
                return field.ne(valueFile);
            }
            if (RSQLOperators.IN.equals(node.getOperator())) {
                return field.eq(valueFile);
            }
            if (RSQLOperators.NOT_IN.equals(node.getOperator())) {
                return field.ne(valueFile);
            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }

    }
    // TODO: support escape and other data type
    static class CollectionsConditionEvaluator extends AbstractConditionEvaluator<Collection<?>> {

        @Override
        public Collection<?> evalArgument(final ComparisonNode node) {
            return node.getArguments();
        }

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() > 1;
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            Field field = evalSelector(node);
            Collection<?> values = evalArgument(node);
            if (RSQLOperators.IN.equals(node.getOperator())) {
                return field.in(values);
            }
            if (RSQLOperators.NOT_IN.equals(node.getOperator())) {
                return field.notIn(values);
            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }
    }



    static abstract class AbstractJsonConditionEvaluator implements ConditionEvaluator<Field> {

        protected String[] jsonKeys(final ComparisonNode node) {
            return keyStringToKeys(node.getSelector(), false);
        }

        protected String jsonField(final String[] jsonKeys) {
            return jsonKeys[0];
        }

        protected String[] jsonPath(final String[] jsonKeys) {
            return Arrays.copyOfRange(jsonKeys, 1, jsonKeys.length);
        }

        abstract JsonNode evalEqualArgument(final String[] jsonPath, final ComparisonNode node);

        protected Condition evalJsonEqual(ComparisonNode node) {
            final String[] jsonKeys = keyStringToKeys(node.getSelector(), true);
            return condition("\"" +jsonField(jsonKeys) + "\" @> '" +
                asStringSerenely(evalEqualArgument(jsonPath(jsonKeys), node)) + "'::jsonb");
        }

        @Override
        public Field evalSelector(ComparisonNode node) {
            String[] keys = jsonKeys(node);
            return field("\"" + jsonField(keys) +
                "\" #> '{" + StringUtils.arrayToCommaDelimitedString(jsonPath(keys)) + "}'");
        }

    }

    static class JsonNullConditionEvaluator extends AbstractJsonConditionEvaluator {

        final static String NULL_VALUE = "null";

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() == 1 && node.getArguments().get(0).equals(NULL_VALUE);
        }

        @Override
        public Field evalArgument(final ComparisonNode node) {
            return field("'null'::jsonb");
        }

        @Override
        JsonNode evalEqualArgument(final String[] jsonPath, final ComparisonNode node) {
            return wrapNullByKeys(jsonPath);
        }

        @Override
        public Condition eval(final ComparisonNode node) {

            if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                return evalJsonEqual(node);
            }
            if (RSQLOperators.NOT_EQUAL.equals(node.getOperator())) {
                return evalSelector(node).ne(evalArgument(node));
            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }

    }

    static class JsonNumberConditionEvaluator extends AbstractJsonConditionEvaluator {

        private static final Pattern INTEGER_VALUE_PATTERN = Pattern.compile("^\\$[+-]?\\d+[Ll]?$");
        private static final Pattern DECIMAL_VALUE_PATTERN = Pattern.compile("^\\$[+-]?(\\d+)?\\.\\d+[dDfF]?$");

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() == 1 &&
                (INTEGER_VALUE_PATTERN.matcher(node.getArguments().get(0)).matches() ||
                    DECIMAL_VALUE_PATTERN.matcher(node.getArguments().get(0)).matches());
        }

        @Override
        JsonNode evalEqualArgument(final String[] jsonPath, final ComparisonNode node) {
            String numberStr = node.getArguments().get(0).replace("$", "").toLowerCase();
            if (numberStr.contains(".")) {
                numberStr = numberStr.replace("d", "").replace("f", "");
                return wrapByKeys(jsonPath, Double.parseDouble(numberStr));
            }
            numberStr = numberStr.replace("l", "");
            return wrapByKeys(jsonPath, Long.parseLong(numberStr));
        }

        @Override
        public Field evalArgument(final ComparisonNode node) {
            String numberStr = node.getArguments().get(0).replace("$", "").toLowerCase();
            if (numberStr.contains(".")) {
                numberStr = numberStr.replace("d", "").replace("f", "");
                return field("'" + Double.parseDouble(numberStr) + "'::jsonb");
            }
            numberStr = numberStr.replace("l", "");
            return field("'" + Long.parseLong(numberStr) + "'::jsonb");
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                return evalJsonEqual(node);
            }
//            if (RSQLOperators.IN.equals(node.getOperator())) {
//                return evalJsonEqual(node);
//            }
            Field field = evalSelector(node);
            Field value = evalArgument(node);
            if (RSQLOperators.NOT_EQUAL.equals(node.getOperator())) {
                return field.ne(value);
            }
            if (RSQLOperators.GREATER_THAN.equals(node.getOperator())) {
                return field.greaterThan(value);
            }
            if (RSQLOperators.GREATER_THAN_OR_EQUAL.equals(node.getOperator())) {
                return field.greaterOrEqual(value);
            }
            if (RSQLOperators.LESS_THAN.equals(node.getOperator())) {
                return field.lessThan(value);
            }
            if (RSQLOperators.LESS_THAN_OR_EQUAL.equals(node.getOperator())) {
                return field.lessOrEqual(value);
            }
//            if (RSQLOperators.NOT_IN.equals(node.getOperator())) {
//                return field.ne(value);
//            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }

    }

    static class JsonStringConditionEvaluator extends AbstractJsonConditionEvaluator {

        @Override
        JsonNode evalEqualArgument(final String[] jsonPath, final ComparisonNode node) {
            return wrapByKeys(jsonPath, node.getArguments().get(0).replace("'", "''"));
        }

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() == 1;
        }

        @Override
        public Field evalArgument(final ComparisonNode node) {
            throw new IllegalArgumentException(alertMessage("Unsupported value eval: %s", node));
        }

        @Override
        public Field evalSelector(final ComparisonNode node) {
            String[] keys = jsonKeys(node);
            return field("\"" + jsonField(keys) +
                "\" #>> '{" + StringUtils.arrayToCommaDelimitedString(jsonPath(keys)) + "}'");
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            String value = node.getArguments().get(0);
            if (value.contains("*")) {
                Field field = evalSelector(node);
                if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                    return field.like(value.replace("*", "%"));
                }
                if (RSQLOperators.NOT_EQUAL.equals(node.getOperator())) {
                    return field.notLike(value.replace("*", "%"));
                }
            }
            if (RSQLOperators.EQUAL.equals(node.getOperator())) {
                return evalJsonEqual(node);
            }
            if (RSQLOperators.IN.equals(node.getOperator())) {
                return evalJsonEqual(node);
            }
            Field field = evalSelector(node);
            if (RSQLOperators.NOT_EQUAL.equals(node.getOperator())) {
                return field.ne(value);
            }
            if (RSQLOperators.NOT_IN.equals(node.getOperator())) {
                return field.ne(value);
            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }
    }
    // TODO: support escape and other data type
    static class JsonCollectionsConditionEvaluator extends AbstractJsonConditionEvaluator {

        @Override
        JsonNode evalEqualArgument(final String[] jsonPath, final ComparisonNode node) {
            return null;
        }

        @Override
        public Field evalSelector(final ComparisonNode node) {
            String[] keys = jsonKeys(node);
            return field("\"" + jsonField(keys) +
                "\" #>> '{" + StringUtils.arrayToCommaDelimitedString(jsonPath(keys)) + "}'");
        }

        @Override
        public Field evalArgument(final ComparisonNode node) {
            return null;
        }

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return node.getArguments().size() > 1;
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            Field field = evalSelector(node);
            if (RSQLOperators.IN.equals(node.getOperator())) {
                return field.in(node.getArguments());
            }
            if (RSQLOperators.NOT_IN.equals(node.getOperator())) {
                return field.notIn(node.getArguments());
            }
            throw new IllegalArgumentException(alertMessage("Unsupported operator: %s", node));
        }
    }



    static abstract class AbstractCompositeConditionEvaluator implements ConditionEvaluator<Void> {

        @Override
        public Field evalSelector(final ComparisonNode node) {
            throw new UnsupportedOperationException("Evaluate selectors");
        }

        @Override
        public Void evalArgument(final ComparisonNode node) {
            throw new UnsupportedOperationException("Evaluate arguments");
        }

    }

    static class CompositeConditionEvaluator extends AbstractCompositeConditionEvaluator {

        static final AbstractConditionEvaluator<?>[] evaluators = {
            new NullConditionEvaluator(),
            new NumberConditionEvaluator(),
            new TimestampConditionEvaluator(),
            new StringConditionEvaluator(),
            new CollectionsConditionEvaluator()
        };

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            return true;
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            for (ConditionEvaluator<?> evaluator : evaluators) {
                if (evaluator.matchArgument(node)) {
                    return evaluator.eval(node);
                }
            }
            throw new IllegalArgumentException(alertMessage("Unsupported value type: %s", node));
        }
    }

    static class JsonCompositeConditionEvaluator extends AbstractCompositeConditionEvaluator {

        static final AbstractJsonConditionEvaluator[] evaluators = {
            new JsonNullConditionEvaluator(),
            new JsonNumberConditionEvaluator(),
            new JsonStringConditionEvaluator(),
            new JsonCollectionsConditionEvaluator()
        };

        @Override
        public boolean matchArgument(final ComparisonNode node) {
            String selector = node.getSelector();
            return WILDCARD_JSON_FILED_PATTERN.matcher(selector).matches();
        }

        @Override
        public Condition eval(final ComparisonNode node) {
            for (ConditionEvaluator<?> evaluator : evaluators) {
                if (evaluator.matchArgument(node)) {
                    return evaluator.eval(node);
                }
            }
            throw new IllegalArgumentException(alertMessage("Unsupported value type: %s", node));
        }
    }

}
